<!--
Copyright 2013 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<!DOCTYPE html>
<style>
  div.anim {
    left: 100px;
    top: 200px;
  }
</style>

<div id="elem"></div>

<div id="anim0" class="anim"></div>
<div id="anim1" class="anim"></div>
<div id="anim2" class="anim"></div>
<div id="anim3" class="anim"></div>
<div id="anim4" class="anim"></div>
<div id="anim5" class="anim"></div>
<div id="anim6" class="anim"></div>
<div id="anim7" class="anim"></div>
<div id="anim8" class="anim"></div>
<div id="anim9" class="anim"></div>
<div id="anim10" class="anim"></div>
<div id="anim11" class="anim"></div>

<script src="../bootstrap.js" nochecks></script>
<script>
"use strict";

var keyframes = [{left: "200px"}, {left: "300px"}];

test(function() {
  assert_true(new Animation(document.getElementById("elem"), keyframes).effect
      instanceof KeyframeAnimationEffect);
}, "Animation.effect should be an instance of KeyframeAnimationEffect when " +
    "created with a keyframe dictionary");

test(function() {
  assert_equals(new KeyframeAnimationEffect(keyframes).accumulate, "none");
}, "KeyframeAnimationEffect.accumulate should default to 'none'");

test(function() {
  var effect = new KeyframeAnimationEffect(keyframes);
  effect.accumulate = "foo";
  assert_equals(effect.accumulate, "none");
  effect.accumulate = 42;
  assert_equals(effect.accumulate, "none");
  effect.accumulate = "sum";
  assert_equals(effect.accumulate, "sum");
}, "KeyframeAnimationEffect.accumulate should only accept valid values");

timing_test(function() {
  var animation = new Animation(document.getElementById("anim0"), keyframes,
      {iterationDuration: 1.0, iterationCount: 2.0});
  document.timeline.play(animation);
  at(1.5, function() {
    assert_styles("#anim0", {left: "250px"});
    animation.effect.accumulate = "sum";
    assert_styles("#anim0", {left: "550px"});
  });
}, "Setting KeyframeAnimationEffect.accumulate should cause immediate update");

test(function() {
  assert_equals(new KeyframeAnimationEffect(keyframes).composite, "replace");
}, "KeyframeAnimationEffect.composite should default to 'replace'");

test(function() {
  var effect = new KeyframeAnimationEffect(keyframes);
  effect.composite = "foo";
  assert_equals(effect.composite, "replace");
  effect.composite = 42;
  assert_equals(effect.composite, "replace");
  effect.composite = "add";
  assert_equals(effect.composite, "add");
}, "KeyframeAnimationEffect.composite should only accept valid values");

timing_test(function() {
  var animation = new Animation(document.getElementById("anim1"), keyframes, 1);
  document.timeline.play(animation);
  at(0.5, function() {
    assert_styles("#anim1", {left: "250px"});
    animation.effect.composite = "add";
    assert_styles("#anim1", {left: "350px"});
  });
}, "Setting KeyframeAnimationEffect.composite should cause immediate update");

test(function() {
  var frames = new KeyframeAnimationEffect(
      [{left: "100px"}, {top: "200px"}]).getFrames();
  assert_equals(frames.length, 2);
  assert_equals(frames[0].left, "100px");
  assert_equals(frames[1].top, "200px");
}, "KeyframeAnimationEffect.getFrames() should return the specified keyframes");

test(function() {
  var frames = new KeyframeAnimationEffect({left: "100px"}).getFrames();
  assert_equals(frames.length, 1);
  assert_equals(frames[0].left, "100px");
}, "KeyframeAnimationEffect should handle a single keyframe");

test(function() {
  var frames = new KeyframeAnimationEffect({}).getFrames();
  assert_equals(frames.length, 1);
  assert_equals(typeof frames[0], "object");
}, "KeyframeAnimationEffect should handle an empty keyframe");

test(function() {
  var frames = new KeyframeAnimationEffect().getFrames();
  assert_equals(frames.length, 1);
  assert_equals(typeof frames[0], "object");
}, "KeyframeAnimationEffect should handle no input for keyframe dictionary");

test(function() {
  var frames = new KeyframeAnimationEffect(42).getFrames();
  assert_equals(frames.length, 1);
  assert_equals(typeof frames[0], "object");
}, "KeyframeAnimationEffect should handle non-objects for keyframe dictionary");

test(function() {
  assert_false(
      new KeyframeAnimationEffect(keyframes).getFrames() === keyframes);
}, "KeyframeAnimationEffect.getFrames() should not return the original " +
    "keyframe dictionary");

test(function() {
  var effect = new KeyframeAnimationEffect({left: "100px"});
  effect.getFrames().push({top: "200px"});
  var frames = effect.getFrames();
  assert_equals(frames.length, 1);
  assert_equals(frames[0].left, "100px");
  assert_equals(frames[0].top, undefined);
}, "KeyframeAnimationEffect.getFrames() should not allow internal state to " +
    "be modified");

test(function() {
  assert_equals(new KeyframeAnimationEffect(keyframes).getFrames()[0].offset,
      null);
}, "Default keyframe offset should be null");

test(function() {
  assert_equals(
      new KeyframeAnimationEffect({offset: "foo"}).getFrames()[0].offset, null);
  assert_equals(new KeyframeAnimationEffect({offset: 42}).getFrames()[0].offset,
      42);
}, "Keyframes should only accept valid values for offset");

test(function() {
  assert_equals(new KeyframeAnimationEffect(keyframes).getFrames()[0].composite,
      null);
}, "Default keyframe composite should be null");

test(function() {
  assert_equals(
      new KeyframeAnimationEffect({composite: "foo"}).getFrames()[0].composite,
      null);
  assert_equals(
      new KeyframeAnimationEffect({composite: 42}).getFrames()[0].composite,
      null);
  assert_equals(
      new KeyframeAnimationEffect({composite: "add"}).getFrames()[0].composite,
      "add");
  assert_equals(new KeyframeAnimationEffect(
      {composite: "replace"}).getFrames()[0].composite,
      "replace");
}, "Keyframes should only accept valid values for composite");

test(function() {
  assert_equals(new KeyframeAnimationEffect({left: null}).getFrames()[0].left,
      "");
}, "Keyframe should handle null property values");

test(function() {
  assert_equals(
      typeof new KeyframeAnimationEffect({left: 42}).getFrames()[0].left,
      "string");
  assert_equals(new KeyframeAnimationEffect({left: 42}).getFrames()[0].left,
      "42");
  var obj = { toString: function() { return "toString() called"; } };
  assert_equals(new KeyframeAnimationEffect({left: obj}).getFrames()[0].left,
      obj.toString());
}, "Keyframe property values should be stringified");

timing_test(function() {
  document.timeline.play(new Animation(
      document.getElementById("anim2"), {left: null, top: ''}, 1));
  at(1.0, function() {
    assert_styles("#anim2", {left: "100px"});
    assert_styles("#anim2", {top: "200px"});
  });
}, "Invalid keyframe property values should be ignored");

test(function() {
  var effect = new KeyframeAnimationEffect(keyframes);
  effect.setFrames({top: "200px"});
  var frames = effect.getFrames();
  assert_equals(frames[0].left, undefined);
  assert_equals(frames[0].top, "200px");
}, "KeyframeAnimationEffect.setFrames() should replace exisiting keyframes");

timing_test(function() {
  var animation = new Animation(document.getElementById("anim3"), keyframes, 1);
  document.timeline.play(animation);
  at(0.5, function() {
    assert_styles("#anim3", {left: "250px"});
    animation.effect.setFrames([{left: "300px"}, {left: "400px"}]);
    assert_styles("#anim3", {left: "350px"});
  });
}, "KeyframeAnimationEffect.setFrames() should immediately update its target");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim4"),
      new KeyframeAnimationEffect(keyframes, 'replace', 'sum'),
      {iterationDuration: 1.0, iterationCount: 3.0}));
  at(0.5, function() {
    assert_styles("#anim4", {left: "250px"});
  });
  at(1.5, function() {
    assert_styles("#anim4", {left: "550px"});
  });
  at(2.5, function() {
    assert_styles("#anim4", {left: "850px"});
  });
}, "Accumulate should work with 'replace' composite keyframes");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim5"),
      new KeyframeAnimationEffect(keyframes, 'add', 'sum'),
      {iterationDuration: 1.0, iterationCount: 3.0}));
  at(0.5, function() {
    assert_styles("#anim5", {left: "350px"});
  });
  at(1.5, function() {
    assert_styles("#anim5", {left: "650px"});
  });
  at(2.5, function() {
    assert_styles("#anim5", {left: "950px"});
  });
}, "Accumulate should work with 'add' composite keyframes");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim6"),
      new KeyframeAnimationEffect(keyframes, 'add', 'sum'),
      {iterationDuration: 1.0, iterationStart: 1.0, iterationCount: 3.0}));
  at(0.5, function() {
    assert_styles("#anim6", {left: "650px"});
  });
  at(1.5, function() {
    assert_styles("#anim6", {left: "950px"});
  });
  at(2.5, function() {
    assert_styles("#anim6", {left: "1250px"});
  });
}, "Accumulate should be from zero iteration: non-zero iterationStart");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim7"),
      new KeyframeAnimationEffect(keyframes, 'add', 'sum'),
      {iterationDuration: 1.0, iterationStart: 1.1, iterationCount: 3.0}));
  at(0.4, function() {
    assert_styles("#anim7", {left: "650px"});
  });
  at(1.4, function() {
    assert_styles("#anim7", {left: "950px"});
  });
  at(2.4, function() {
    assert_styles("#anim7", {left: "1250px"});
  });
}, "Accumulate should be from zero iteration: non-integer iterationStart");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim8"),
      new KeyframeAnimationEffect([
        {left: "200px", composite: "add"},
        {left: "300px", composite: "replace"}
      ], 'replace', 'sum'),
      {iterationDuration: 1.0, iterationCount: 3.0}));
  at(0.5, function() {
    assert_styles("#anim8", {left: "300px"});
  });
  at(1.5, function() {
    assert_styles("#anim8", {left: "300px"});
  });
}, "Accumulate should not be used if composite is mixed");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim9"),
      new KeyframeAnimationEffect(keyframes, 'add', 'sum'),
      {iterationDuration: 1.0, iterationStart: -2.0, iterationCount: 4.0}));
  at(0.5, function() {
    assert_styles("#anim9", {left: "350px"});
  });
  at(1.5, function() {
    assert_styles("#anim9", {left: "350px"});
  });
  at(2.5, function() {
    assert_styles("#anim9", {left: "350px"});
  });
  at(3.5, function() {
    assert_styles("#anim9", {left: "650px"});
  });
}, "Accumulate should not be used on non-positive iterations");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim10"), [
    {left: "100px"},
    {left: "200px"},
    {left: "100px"},
    {left: "300px"},
    {left: "100px"},
  ], 1));
  at(0.0, function() {
    assert_styles("#anim10", {left: "100px"});
  });
  at(0.25, function() {
    assert_styles("#anim10", {left: "200px"});
  });
  at(0.5, function() {
    assert_styles("#anim10", {left: "100px"});
  });
  at(0.75, function() {
    assert_styles("#anim10", {left: "300px"});
  });
  at(1.0, function() {
    assert_styles("#anim10", {left: "100px"});
  });
}, "Keyframes should be distributed evenly between offsets 0 and 1");

timing_test(function() {
  document.timeline.play(new Animation(document.getElementById("anim11"), [
    {left: "100px", offset: 0.0},
    {left: "200px", offset: 0.7},
    {left: "100px"},
    {left: "300px"},
    {left: "100px", offset: 1.0},
  ], 1));
  at(0.0, function() {
    assert_styles("#anim11", {left: "100px"});
  });
  at(0.7, function() {
    assert_styles("#anim11", {left: "200px"});
  });
  at(0.8, function() {
    assert_styles("#anim11", {left: "100px"});
  });
  at(0.9, function() {
    assert_styles("#anim11", {left: "300px"});
  });
  at(1.0, function() {
    assert_styles("#anim11", {left: "100px"});
  });
}, "Keyframes should be distributed evenly between keyframes with offsets");

</script>
